/***********************************************************************************************************************
*  OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
*  See also https://openstudio.net/license
***********************************************************************************************************************/

#include "WaterHeaterStratified.hpp"
#include "WaterHeaterStratified_Impl.hpp"
#include "WaterHeaterHeatPump.hpp"
#include "WaterHeaterHeatPump_Impl.hpp"
#include "WaterHeaterHeatPumpWrappedCondenser.hpp"
#include "WaterHeaterHeatPumpWrappedCondenser_Impl.hpp"
#include "Schedule.hpp"
#include "Schedule_Impl.hpp"
#include "ThermalZone.hpp"
#include "ThermalZone_Impl.hpp"
#include "ScheduleTypeLimits.hpp"
#include "ScheduleTypeRegistry.hpp"
#include "ScheduleDay.hpp"
#include "ScheduleDay_Impl.hpp"
#include "ScheduleRuleset.hpp"
#include "ScheduleRuleset_Impl.hpp"
#include "Model.hpp"
#include "Model_Impl.hpp"
#include "WaterHeaterSizing.hpp"
#include "WaterHeaterSizing_Impl.hpp"
#include "PlantLoop.hpp"
#include "PlantLoop_Impl.hpp"
#include "Node.hpp"
#include "Node_Impl.hpp"

#include "../utilities/core/Assert.hpp"
#include "../utilities/data/DataEnums.hpp"

#include <utilities/idd/IddFactory.hxx>
#include <utilities/idd/OS_WaterHeater_Stratified_FieldEnums.hxx>

#include <algorithm>

namespace openstudio {
namespace model {

  namespace detail {

    WaterHeaterStratified_Impl::WaterHeaterStratified_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle)
      : WaterToWaterComponent_Impl(idfObject, model, keepHandle) {
      OS_ASSERT(idfObject.iddObject().type() == WaterHeaterStratified::iddObjectType());
    }

    WaterHeaterStratified_Impl::WaterHeaterStratified_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle)
      : WaterToWaterComponent_Impl(other, model, keepHandle) {
      OS_ASSERT(other.iddObject().type() == WaterHeaterStratified::iddObjectType());
    }

    WaterHeaterStratified_Impl::WaterHeaterStratified_Impl(const WaterHeaterStratified_Impl& other, Model_Impl* model, bool keepHandle)
      : WaterToWaterComponent_Impl(other, model, keepHandle) {}

    const std::vector<std::string>& WaterHeaterStratified_Impl::outputVariableNames() const {
      static const std::vector<std::string> result = [] {
        // initialize with an immediately invoked lambda so that the `result` can be const
        // and safely thread-safe initialized
        std::vector<std::string> val{
          // In common with WaterHeater:Mixed
          "Water Heater Tank Temperature", "Water Heater Final Tank Temperature", "Water Heater Heat Loss Rate", "Water Heater Heat Loss Energy",
          "Water Heater Use Side Mass Flow Rate", "Water Heater Use Side Inlet Temperature", "Water Heater Use Side Outlet Temperature",
          "Water Heater Use Side Heat Transfer Rate", "Water Heater Use Side Heat Transfer Energy", "Water Heater Source Side Mass Flow Rate",
          "Water Heater Source Side Inlet Temperature", "Water Heater Source Side Outlet Temperature", "Water Heater Source Side Heat Transfer Rate",
          "Water Heater Source Side Heat Transfer Energy", "Water Heater Off Cycle Parasitic Tank Heat Transfer Rate",
          "Water Heater Off Cycle Parasitic Tank Heat Transfer Energy", "Water Heater On Cycle Parasitic Tank Heat Transfer Rate",
          "Water Heater On Cycle Parasitic Tank Heat Transfer Energy", "Water Heater Total Demand Heat Transfer Rate",
          "Water Heater Total Demand Energy", "Water Heater Heating Rate", "Water Heater Heating Energy",
          "Water Heater Unmet Demand Heat Transfer Rate", "Water Heater Unmet Demand Heat Transfer Energy", "Water Heater Venting Heat Transfer Rate",
          "Water Heater Venting Heat Transfer Energy", "Water Heater Net Heat Transfer Rate", "Water Heater Net Heat Transfer Energy",
          "Water Heater Cycle On Count", "Water Heater Runtime Fraction", "Water Heater Part Load Ratio", "Water Heater Electricity Rate",

          // This should be based on fuel type

          //"Water Heater <Fuel Type> Rate",
          //"Water Heater <Fuel Type> Energy",
          // Fuel type specific
          // TODO: DLM: the return type of this method needs to change to std::vector<std::string> in ModelObject
          // until then, make this include all possible outputVariableNames for class regardless of fuelType
          // std::string fuelType = this->fuelType();
          // if (fuelType == "Electricity") {
          "Water Heater Electricity Rate", "Water Heater Electricity Energy",
          // } else if (fuelType == "NaturalGas") {
          "Water Heater NaturalGas Rate", "Water Heater NaturalGas Energy",
          // } else if (fuelType == "Propane") {
          "Water Heater Propane Rate", "Water Heater Propane Energy",
          // } else if (fuelType == "FuelOilNo1") {
          "Water Heater FuelOilNo1 Rate", "Water Heater FuelOilNo1 Energy",
          // } else if (fuelType == "FuelOilNo2") {
          "Water Heater FuelOilNo2 Rate", "Water Heater FuelOilNo2 Energy",
          // } else if (fuelType == "Coal") {
          "Water Heater Coal Rate", "Water Heater Coal Energy",
          // } else if (fuelType == "Diesel") {
          "Water Heater Diesel Rate", "Water Heater Diesel Energy",
          // } else if (fuelType == "Gasoline") {
          "Water Heater Gasoline Rate", "Water Heater Gasoline Energy",
          // } else if (fuelType == "OtherFuel1") {
          "Water Heater OtherFuel1 Rate", "Water Heater OtherFuel1 Energy",
          // } else if (fuelType == "OtherFuel2") {
          "Water Heater OtherFuel2 Rate", "Water Heater OtherFuel2 Energy",
          // }

          // TODO: implement these too...
          //"Water Heater Off Cycle Parasitic <Fuel Type> Rate",
          //"Water Heater Off Cycle Parasitic <Fuel Type> Energy",
          //"Water Heater On Cycle Parasitic <Fuel Type> Rate",
          // "Water Heater On Cycle Parasitic <Fuel Type> Energy",
          //
          "Water Heater Water Volume Flow Rate", "Water Heater Water Volume",

          // Specific to Stratified
          "Water Heater Heater 1 Heating Rate", "Water Heater Heater 2 Heating Rate", "Water Heater Heater 1 Heating Energy",
          "Water Heater Heater 2 Heating Energy", "Water Heater Heater 1 Cycle On Count", "Water Heater Heater 2 Cycle On Count",
          "Water Heater Heater 1 Runtime Fraction", "Water Heater Heater 2 Runtime Fraction"};
        // TODO: his should really be a check on whether the node is defined...
        for (int i = 1; i <= 12; ++i) {
          val.push_back("Water Heater Temperature Node " + std::to_string(i));
          val.push_back("Water Heater Final Temperature Node " + std::to_string(i));
        }
        return val;
      }();

      return result;
    }

    IddObjectType WaterHeaterStratified_Impl::iddObjectType() const {
      return WaterHeaterStratified::iddObjectType();
    }

    std::vector<ModelObject> WaterHeaterStratified_Impl::children() const {
      return std::vector<ModelObject>{waterHeaterSizing()};
    }

    ModelObject WaterHeaterStratified_Impl::clone(Model model) const {
      auto whClone = WaterToWaterComponent_Impl::clone(model).cast<WaterHeaterStratified>();

      auto sizingClone = waterHeaterSizing().clone(model).cast<WaterHeaterSizing>();
      sizingClone.getImpl<WaterHeaterSizing_Impl>()->setWaterHeater(whClone);
      return std::move(whClone);
    }

    std::vector<ScheduleTypeKey> WaterHeaterStratified_Impl::getScheduleTypeKeys(const Schedule& schedule) const {
      std::vector<ScheduleTypeKey> result;
      UnsignedVector fieldIndices = getSourceIndices(schedule.handle());
      UnsignedVector::const_iterator b(fieldIndices.begin());
      UnsignedVector::const_iterator e(fieldIndices.end());
      if (std::find(b, e, OS_WaterHeater_StratifiedFields::Heater1SetpointTemperatureScheduleName) != e) {
        result.push_back(ScheduleTypeKey("WaterHeaterStratified", "Heater 1 Setpoint Temperature"));
      }
      if (std::find(b, e, OS_WaterHeater_StratifiedFields::Heater2SetpointTemperatureScheduleName) != e) {
        result.push_back(ScheduleTypeKey("WaterHeaterStratified", "Heater 2 Setpoint Temperature"));
      }
      if (std::find(b, e, OS_WaterHeater_StratifiedFields::AmbientTemperatureScheduleName) != e) {
        result.push_back(ScheduleTypeKey("WaterHeaterStratified", "Ambient Temperature"));
      }
      if (std::find(b, e, OS_WaterHeater_StratifiedFields::UseFlowRateFractionScheduleName) != e) {
        result.push_back(ScheduleTypeKey("WaterHeaterStratified", "Use Flow Rate Fraction"));
      }
      if (std::find(b, e, OS_WaterHeater_StratifiedFields::ColdWaterSupplyTemperatureScheduleName) != e) {
        result.push_back(ScheduleTypeKey("WaterHeaterStratified", "Cold Water Supply Temperature"));
      }
      if (std::find(b, e, OS_WaterHeater_StratifiedFields::IndirectAlternateSetpointTemperatureScheduleName) != e) {
        result.push_back(ScheduleTypeKey("WaterHeaterStratified", "Indirect Alternate Setpoint Temperature"));
      }
      return result;
    }

    std::string WaterHeaterStratified_Impl::endUseSubcategory() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::EndUseSubcategory, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::tankVolume() const {
      return getDouble(OS_WaterHeater_StratifiedFields::TankVolume, true);
    }

    bool WaterHeaterStratified_Impl::isTankVolumeAutosized() const {
      bool result = false;
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::TankVolume, true);
      if (value) {
        result = openstudio::istringEqual(value.get(), "autosize");
      }
      return result;
    }

    boost::optional<double> WaterHeaterStratified_Impl::tankHeight() const {
      return getDouble(OS_WaterHeater_StratifiedFields::TankHeight, true);
    }

    bool WaterHeaterStratified_Impl::isTankHeightAutosized() const {
      bool result = false;
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::TankHeight, true);
      if (value) {
        result = openstudio::istringEqual(value.get(), "autosize");
      }
      return result;
    }

    std::string WaterHeaterStratified_Impl::tankShape() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::TankShape, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::tankPerimeter() const {
      return getDouble(OS_WaterHeater_StratifiedFields::TankPerimeter, true);
    }

    double WaterHeaterStratified_Impl::maximumTemperatureLimit() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::MaximumTemperatureLimit, true);
      OS_ASSERT(value);
      return value.get();
    }

    std::string WaterHeaterStratified_Impl::heaterPriorityControl() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::HeaterPriorityControl, true);
      OS_ASSERT(value);
      return value.get();
    }

    Schedule WaterHeaterStratified_Impl::heater1SetpointTemperatureSchedule() const {
      boost::optional<Schedule> value = optionalHeater1SetpointTemperatureSchedule();
      if (!value) {
        LOG_AND_THROW(briefDescription() << " does not have an Heater1Setpoint Temperature Schedule attached.");
      }
      return value.get();
    }

    double WaterHeaterStratified_Impl::heater1DeadbandTemperatureDifference() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Heater1DeadbandTemperatureDifference, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::heater1Capacity() const {
      return getDouble(OS_WaterHeater_StratifiedFields::Heater1Capacity, true);
    }

    bool WaterHeaterStratified_Impl::isHeater1CapacityAutosized() const {
      bool result = false;
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::Heater1Capacity, true);
      if (value) {
        result = openstudio::istringEqual(value.get(), "autosize");
      }
      return result;
    }

    double WaterHeaterStratified_Impl::heater1Height() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Heater1Height, true);
      OS_ASSERT(value);
      return value.get();
    }

    Schedule WaterHeaterStratified_Impl::heater2SetpointTemperatureSchedule() const {
      boost::optional<Schedule> value = optionalHeater2SetpointTemperatureSchedule();
      if (!value) {
        LOG_AND_THROW(briefDescription() << " does not have an Heater2Setpoint Temperature Schedule attached.");
      }
      return value.get();
    }

    double WaterHeaterStratified_Impl::heater2DeadbandTemperatureDifference() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Heater2DeadbandTemperatureDifference, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::heater2Capacity() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Heater2Capacity, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::heater2Height() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Heater2Height, true);
      OS_ASSERT(value);
      return value.get();
    }

    std::string WaterHeaterStratified_Impl::heaterFuelType() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::HeaterFuelType, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::heaterThermalEfficiency() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::HeaterThermalEfficiency, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::offCycleParasiticFuelConsumptionRate() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::OffCycleParasiticFuelConsumptionRate, true);
      OS_ASSERT(value);
      return value.get();
    }

    std::string WaterHeaterStratified_Impl::offCycleParasiticFuelType() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::OffCycleParasiticFuelType, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::offCycleParasiticHeatFractiontoTank() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::OffCycleParasiticHeatFractiontoTank, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::offCycleParasiticHeight() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::OffCycleParasiticHeight, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::onCycleParasiticFuelConsumptionRate() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::OnCycleParasiticFuelConsumptionRate, true);
      OS_ASSERT(value);
      return value.get();
    }

    std::string WaterHeaterStratified_Impl::onCycleParasiticFuelType() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::OnCycleParasiticFuelType, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::onCycleParasiticHeatFractiontoTank() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::OnCycleParasiticHeatFractiontoTank, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::onCycleParasiticHeight() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::OnCycleParasiticHeight, true);
      OS_ASSERT(value);
      return value.get();
    }

    std::string WaterHeaterStratified_Impl::ambientTemperatureIndicator() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::AmbientTemperatureIndicator, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<Schedule> WaterHeaterStratified_Impl::ambientTemperatureSchedule() const {
      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_WaterHeater_StratifiedFields::AmbientTemperatureScheduleName);
    }

    boost::optional<ThermalZone> WaterHeaterStratified_Impl::ambientTemperatureThermalZone() const {
      return getObject<ModelObject>().getModelObjectTarget<ThermalZone>(OS_WaterHeater_StratifiedFields::AmbientTemperatureThermalZoneName);
    }

    boost::optional<std::string> WaterHeaterStratified_Impl::ambientTemperatureOutdoorAirNodeName() const {
      return getString(OS_WaterHeater_StratifiedFields::AmbientTemperatureOutdoorAirNodeName, true);
    }

    boost::optional<double> WaterHeaterStratified_Impl::uniformSkinLossCoefficientperUnitAreatoAmbientTemperature() const {
      return getDouble(OS_WaterHeater_StratifiedFields::UniformSkinLossCoefficientperUnitAreatoAmbientTemperature, true);
    }

    double WaterHeaterStratified_Impl::skinLossFractiontoZone() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::SkinLossFractiontoZone, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::offCycleFlueLossCoefficienttoAmbientTemperature() const {
      return getDouble(OS_WaterHeater_StratifiedFields::OffCycleFlueLossCoefficienttoAmbientTemperature, true);
    }

    double WaterHeaterStratified_Impl::offCycleFlueLossFractiontoZone() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::OffCycleFlueLossFractiontoZone, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::peakUseFlowRate() const {
      return getDouble(OS_WaterHeater_StratifiedFields::PeakUseFlowRate, true);
    }

    boost::optional<Schedule> WaterHeaterStratified_Impl::useFlowRateFractionSchedule() const {
      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_WaterHeater_StratifiedFields::UseFlowRateFractionScheduleName);
    }

    boost::optional<Schedule> WaterHeaterStratified_Impl::coldWaterSupplyTemperatureSchedule() const {
      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_WaterHeater_StratifiedFields::ColdWaterSupplyTemperatureScheduleName);
    }

    double WaterHeaterStratified_Impl::useSideEffectiveness() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::UseSideEffectiveness, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::useSideInletHeight() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::UseSideInletHeight, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::useSideOutletHeight() const {
      return getDouble(OS_WaterHeater_StratifiedFields::UseSideOutletHeight, true);
    }

    bool WaterHeaterStratified_Impl::isUseSideOutletHeightAutocalculated() const {
      bool result = false;
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::UseSideOutletHeight, true);
      if (value) {
        result = openstudio::istringEqual(value.get(), "autocalculate");
      }
      return result;
    }

    double WaterHeaterStratified_Impl::sourceSideEffectiveness() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::SourceSideEffectiveness, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::sourceSideInletHeight() const {
      return getDouble(OS_WaterHeater_StratifiedFields::SourceSideInletHeight, true);
    }

    bool WaterHeaterStratified_Impl::isSourceSideInletHeightAutocalculated() const {
      bool result = false;
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::SourceSideInletHeight, true);
      if (value) {
        result = openstudio::istringEqual(value.get(), "autocalculate");
      }
      return result;
    }

    double WaterHeaterStratified_Impl::sourceSideOutletHeight() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::SourceSideOutletHeight, true);
      OS_ASSERT(value);
      return value.get();
    }

    std::string WaterHeaterStratified_Impl::inletMode() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::InletMode, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<double> WaterHeaterStratified_Impl::useSideDesignFlowRate() const {
      return getDouble(OS_WaterHeater_StratifiedFields::UseSideDesignFlowRate, true);
    }

    bool WaterHeaterStratified_Impl::isUseSideDesignFlowRateAutosized() const {
      bool result = false;
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::UseSideDesignFlowRate, true);
      if (value) {
        result = openstudio::istringEqual(value.get(), "autosize");
      }
      return result;
    }

    boost::optional<double> WaterHeaterStratified_Impl::sourceSideDesignFlowRate() const {
      return getDouble(OS_WaterHeater_StratifiedFields::SourceSideDesignFlowRate, true);
    }

    bool WaterHeaterStratified_Impl::isSourceSideDesignFlowRateAutosized() const {
      bool result = false;
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::SourceSideDesignFlowRate, true);
      if (value) {
        result = openstudio::istringEqual(value.get(), "autosize");
      }
      return result;
    }

    double WaterHeaterStratified_Impl::indirectWaterHeatingRecoveryTime() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::IndirectWaterHeatingRecoveryTime, true);
      OS_ASSERT(value);
      return value.get();
    }

    int WaterHeaterStratified_Impl::numberofNodes() const {
      boost::optional<int> value = getInt(OS_WaterHeater_StratifiedFields::NumberofNodes, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::additionalDestratificationConductivity() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::AdditionalDestratificationConductivity, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node1AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node1AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node2AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node2AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node3AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node3AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node4AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node4AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node5AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node5AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node6AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node6AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node7AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node7AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node8AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node8AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node9AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node9AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node10AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node10AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node11AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node11AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    double WaterHeaterStratified_Impl::node12AdditionalLossCoefficient() const {
      boost::optional<double> value = getDouble(OS_WaterHeater_StratifiedFields::Node12AdditionalLossCoefficient, true);
      OS_ASSERT(value);
      return value.get();
    }

    std::string WaterHeaterStratified_Impl::sourceSideFlowControlMode() const {
      boost::optional<std::string> value = getString(OS_WaterHeater_StratifiedFields::SourceSideFlowControlMode, true);
      OS_ASSERT(value);
      return value.get();
    }

    boost::optional<Schedule> WaterHeaterStratified_Impl::indirectAlternateSetpointTemperatureSchedule() const {
      return getObject<ModelObject>().getModelObjectTarget<Schedule>(
        OS_WaterHeater_StratifiedFields::IndirectAlternateSetpointTemperatureScheduleName);
    }

    bool WaterHeaterStratified_Impl::setEndUseSubcategory(const std::string& endUseSubcategory) {
      bool result = setString(OS_WaterHeater_StratifiedFields::EndUseSubcategory, endUseSubcategory);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setTankVolume(boost::optional<double> tankVolume) {
      bool result(false);
      if (tankVolume) {
        result = setDouble(OS_WaterHeater_StratifiedFields::TankVolume, tankVolume.get());
      }
      return result;
    }

    void WaterHeaterStratified_Impl::autosizeTankVolume() {
      bool result = setString(OS_WaterHeater_StratifiedFields::TankVolume, "autosize");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setTankHeight(boost::optional<double> tankHeight) {
      bool result(false);
      if (tankHeight) {
        result = setDouble(OS_WaterHeater_StratifiedFields::TankHeight, tankHeight.get());
      }
      return result;
    }

    void WaterHeaterStratified_Impl::autosizeTankHeight() {
      bool result = setString(OS_WaterHeater_StratifiedFields::TankHeight, "autosize");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setTankShape(const std::string& tankShape) {
      bool result = setString(OS_WaterHeater_StratifiedFields::TankShape, tankShape);
      return result;
    }

    bool WaterHeaterStratified_Impl::setTankPerimeter(boost::optional<double> tankPerimeter) {
      bool result(false);
      if (tankPerimeter) {
        result = setDouble(OS_WaterHeater_StratifiedFields::TankPerimeter, tankPerimeter.get());
      } else {
        resetTankPerimeter();
        result = true;
      }
      return result;
    }

    void WaterHeaterStratified_Impl::resetTankPerimeter() {
      bool result = setString(OS_WaterHeater_StratifiedFields::TankPerimeter, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setMaximumTemperatureLimit(double maximumTemperatureLimit) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::MaximumTemperatureLimit, maximumTemperatureLimit);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeaterPriorityControl(const std::string& heaterPriorityControl) {
      bool result = setString(OS_WaterHeater_StratifiedFields::HeaterPriorityControl, heaterPriorityControl);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeater1SetpointTemperatureSchedule(Schedule& schedule) {
      bool result = setSchedule(OS_WaterHeater_StratifiedFields::Heater1SetpointTemperatureScheduleName, "WaterHeaterStratified",
                                "Heater 1 Setpoint Temperature", schedule);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeater1DeadbandTemperatureDifference(double heater1DeadbandTemperatureDifference) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Heater1DeadbandTemperatureDifference, heater1DeadbandTemperatureDifference);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeater1Capacity(boost::optional<double> heater1Capacity) {
      bool result(false);
      if (heater1Capacity) {
        result = setDouble(OS_WaterHeater_StratifiedFields::Heater1Capacity, heater1Capacity.get());
      }
      return result;
    }

    void WaterHeaterStratified_Impl::autosizeHeater1Capacity() {
      bool result = setString(OS_WaterHeater_StratifiedFields::Heater1Capacity, "autosize");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setHeater1Height(double heater1Height) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Heater1Height, heater1Height);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeater2SetpointTemperatureSchedule(Schedule& schedule) {
      bool result = setSchedule(OS_WaterHeater_StratifiedFields::Heater2SetpointTemperatureScheduleName, "WaterHeaterStratified",
                                "Heater 2 Setpoint Temperature", schedule);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeater2DeadbandTemperatureDifference(double heater2DeadbandTemperatureDifference) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Heater2DeadbandTemperatureDifference, heater2DeadbandTemperatureDifference);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeater2Capacity(double heater2Capacity) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Heater2Capacity, heater2Capacity);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeater2Height(double heater2Height) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Heater2Height, heater2Height);
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeaterFuelType(const FuelType& heaterFuelType) {
      const bool result = setString(OS_WaterHeater_StratifiedFields::HeaterFuelType, heaterFuelType.valueDescription());
      return result;
    }

    bool WaterHeaterStratified_Impl::setHeaterThermalEfficiency(double heaterThermalEfficiency) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::HeaterThermalEfficiency, heaterThermalEfficiency);
      return result;
    }

    bool WaterHeaterStratified_Impl::setOffCycleParasiticFuelConsumptionRate(double offCycleParasiticFuelConsumptionRate) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::OffCycleParasiticFuelConsumptionRate, offCycleParasiticFuelConsumptionRate);
      return result;
    }

    bool WaterHeaterStratified_Impl::setOffCycleParasiticFuelType(const FuelType& offCycleParasiticFuelType) {
      bool result = setString(OS_WaterHeater_StratifiedFields::OffCycleParasiticFuelType, offCycleParasiticFuelType.valueDescription());
      return result;
    }

    bool WaterHeaterStratified_Impl::setOffCycleParasiticHeatFractiontoTank(double offCycleParasiticHeatFractiontoTank) {
      const bool result = setDouble(OS_WaterHeater_StratifiedFields::OffCycleParasiticHeatFractiontoTank, offCycleParasiticHeatFractiontoTank);
      return result;
    }

    bool WaterHeaterStratified_Impl::setOffCycleParasiticHeight(double offCycleParasiticHeight) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::OffCycleParasiticHeight, offCycleParasiticHeight);
      return result;
    }

    bool WaterHeaterStratified_Impl::setOnCycleParasiticFuelConsumptionRate(double onCycleParasiticFuelConsumptionRate) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::OnCycleParasiticFuelConsumptionRate, onCycleParasiticFuelConsumptionRate);
      return result;
    }

    bool WaterHeaterStratified_Impl::setOnCycleParasiticFuelType(const FuelType& onCycleParasiticFuelType) {
      const bool result = setString(OS_WaterHeater_StratifiedFields::OnCycleParasiticFuelType, onCycleParasiticFuelType.valueDescription());
      return result;
    }

    bool WaterHeaterStratified_Impl::setOnCycleParasiticHeatFractiontoTank(double onCycleParasiticHeatFractiontoTank) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::OnCycleParasiticHeatFractiontoTank, onCycleParasiticHeatFractiontoTank);
      return result;
    }

    bool WaterHeaterStratified_Impl::setOnCycleParasiticHeight(double onCycleParasiticHeight) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::OnCycleParasiticHeight, onCycleParasiticHeight);
      return result;
    }

    bool WaterHeaterStratified_Impl::setAmbientTemperatureIndicator(const std::string& ambientTemperatureIndicator) {
      bool result = setString(OS_WaterHeater_StratifiedFields::AmbientTemperatureIndicator, ambientTemperatureIndicator);
      return result;
    }

    bool WaterHeaterStratified_Impl::setAmbientTemperatureSchedule(Schedule& schedule) {
      bool result =
        setSchedule(OS_WaterHeater_StratifiedFields::AmbientTemperatureScheduleName, "WaterHeaterStratified", "Ambient Temperature", schedule);
      return result;
    }

    void WaterHeaterStratified_Impl::resetAmbientTemperatureSchedule() {
      bool result = setString(OS_WaterHeater_StratifiedFields::AmbientTemperatureScheduleName, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setAmbientTemperatureThermalZone(const boost::optional<ThermalZone>& thermalZone) {
      bool result(false);
      if (thermalZone) {
        result = setPointer(OS_WaterHeater_StratifiedFields::AmbientTemperatureThermalZoneName, thermalZone.get().handle());
      } else {
        resetAmbientTemperatureThermalZone();
        result = true;
      }
      return result;
    }

    void WaterHeaterStratified_Impl::resetAmbientTemperatureThermalZone() {
      bool result = setString(OS_WaterHeater_StratifiedFields::AmbientTemperatureThermalZoneName, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setAmbientTemperatureOutdoorAirNodeName(boost::optional<std::string> ambientTemperatureOutdoorAirNodeName) {
      bool result(false);
      if (ambientTemperatureOutdoorAirNodeName) {
        result = setString(OS_WaterHeater_StratifiedFields::AmbientTemperatureOutdoorAirNodeName, ambientTemperatureOutdoorAirNodeName.get());
      } else {
        resetAmbientTemperatureOutdoorAirNodeName();
        result = true;
      }
      OS_ASSERT(result);
      return result;
    }

    void WaterHeaterStratified_Impl::resetAmbientTemperatureOutdoorAirNodeName() {
      bool result = setString(OS_WaterHeater_StratifiedFields::AmbientTemperatureOutdoorAirNodeName, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(
      boost::optional<double> uniformSkinLossCoefficientperUnitAreatoAmbientTemperature) {
      bool result(false);
      if (uniformSkinLossCoefficientperUnitAreatoAmbientTemperature) {
        result = setDouble(OS_WaterHeater_StratifiedFields::UniformSkinLossCoefficientperUnitAreatoAmbientTemperature,
                           uniformSkinLossCoefficientperUnitAreatoAmbientTemperature.get());
      } else {
        resetUniformSkinLossCoefficientperUnitAreatoAmbientTemperature();
        result = true;
      }
      return result;
    }

    void WaterHeaterStratified_Impl::resetUniformSkinLossCoefficientperUnitAreatoAmbientTemperature() {
      bool result = setString(OS_WaterHeater_StratifiedFields::UniformSkinLossCoefficientperUnitAreatoAmbientTemperature, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setSkinLossFractiontoZone(double skinLossFractiontoZone) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::SkinLossFractiontoZone, skinLossFractiontoZone);
      return result;
    }

    bool WaterHeaterStratified_Impl::setOffCycleFlueLossCoefficienttoAmbientTemperature(
      boost::optional<double> offCycleFlueLossCoefficienttoAmbientTemperature) {
      bool result(false);
      if (offCycleFlueLossCoefficienttoAmbientTemperature) {
        result = setDouble(OS_WaterHeater_StratifiedFields::OffCycleFlueLossCoefficienttoAmbientTemperature,
                           offCycleFlueLossCoefficienttoAmbientTemperature.get());
      } else {
        resetOffCycleFlueLossCoefficienttoAmbientTemperature();
        result = true;
      }
      return result;
    }

    void WaterHeaterStratified_Impl::resetOffCycleFlueLossCoefficienttoAmbientTemperature() {
      bool result = setString(OS_WaterHeater_StratifiedFields::OffCycleFlueLossCoefficienttoAmbientTemperature, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setOffCycleFlueLossFractiontoZone(double offCycleFlueLossFractiontoZone) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::OffCycleFlueLossFractiontoZone, offCycleFlueLossFractiontoZone);
      return result;
    }

    bool WaterHeaterStratified_Impl::setPeakUseFlowRate(boost::optional<double> peakUseFlowRate) {
      bool result(false);
      if (peakUseFlowRate) {
        result = setDouble(OS_WaterHeater_StratifiedFields::PeakUseFlowRate, peakUseFlowRate.get());
      } else {
        resetPeakUseFlowRate();
        result = true;
      }
      return result;
    }

    void WaterHeaterStratified_Impl::resetPeakUseFlowRate() {
      bool result = setString(OS_WaterHeater_StratifiedFields::PeakUseFlowRate, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setUseFlowRateFractionSchedule(Schedule& schedule) {
      bool result =
        setSchedule(OS_WaterHeater_StratifiedFields::UseFlowRateFractionScheduleName, "WaterHeaterStratified", "Use Flow Rate Fraction", schedule);
      return result;
    }

    void WaterHeaterStratified_Impl::resetUseFlowRateFractionSchedule() {
      bool result = setString(OS_WaterHeater_StratifiedFields::UseFlowRateFractionScheduleName, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setColdWaterSupplyTemperatureSchedule(Schedule& schedule) {
      bool result = setSchedule(OS_WaterHeater_StratifiedFields::ColdWaterSupplyTemperatureScheduleName, "WaterHeaterStratified",
                                "Cold Water Supply Temperature", schedule);
      return result;
    }

    void WaterHeaterStratified_Impl::resetColdWaterSupplyTemperatureSchedule() {
      bool result = setString(OS_WaterHeater_StratifiedFields::ColdWaterSupplyTemperatureScheduleName, "");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setUseSideEffectiveness(double useSideEffectiveness) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::UseSideEffectiveness, useSideEffectiveness);
      return result;
    }

    bool WaterHeaterStratified_Impl::setUseSideInletHeight(double useSideInletHeight) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::UseSideInletHeight, useSideInletHeight);
      return result;
    }

    bool WaterHeaterStratified_Impl::setUseSideOutletHeight(boost::optional<double> useSideOutletHeight) {
      bool result(false);
      if (useSideOutletHeight) {
        result = setDouble(OS_WaterHeater_StratifiedFields::UseSideOutletHeight, useSideOutletHeight.get());
      }
      return result;
    }

    void WaterHeaterStratified_Impl::autocalculateUseSideOutletHeight() {
      bool result = setString(OS_WaterHeater_StratifiedFields::UseSideOutletHeight, "autocalculate");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setSourceSideEffectiveness(double sourceSideEffectiveness) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::SourceSideEffectiveness, sourceSideEffectiveness);
      return result;
    }

    bool WaterHeaterStratified_Impl::setSourceSideInletHeight(boost::optional<double> sourceSideInletHeight) {
      bool result(false);
      if (sourceSideInletHeight) {
        result = setDouble(OS_WaterHeater_StratifiedFields::SourceSideInletHeight, sourceSideInletHeight.get());
      }
      return result;
    }

    void WaterHeaterStratified_Impl::autocalculateSourceSideInletHeight() {
      bool result = setString(OS_WaterHeater_StratifiedFields::SourceSideInletHeight, "autocalculate");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setSourceSideOutletHeight(double sourceSideOutletHeight) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::SourceSideOutletHeight, sourceSideOutletHeight);
      return result;
    }

    bool WaterHeaterStratified_Impl::setInletMode(const std::string& inletMode) {
      bool result = setString(OS_WaterHeater_StratifiedFields::InletMode, inletMode);
      return result;
    }

    bool WaterHeaterStratified_Impl::setUseSideDesignFlowRate(boost::optional<double> useSideDesignFlowRate) {
      bool result(false);
      if (useSideDesignFlowRate) {
        result = setDouble(OS_WaterHeater_StratifiedFields::UseSideDesignFlowRate, useSideDesignFlowRate.get());
      }
      return result;
    }

    void WaterHeaterStratified_Impl::autosizeUseSideDesignFlowRate() {
      bool result = setString(OS_WaterHeater_StratifiedFields::UseSideDesignFlowRate, "autosize");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setSourceSideDesignFlowRate(boost::optional<double> sourceSideDesignFlowRate) {
      bool result(false);
      if (sourceSideDesignFlowRate) {
        result = setDouble(OS_WaterHeater_StratifiedFields::SourceSideDesignFlowRate, sourceSideDesignFlowRate.get());
      }
      return result;
    }

    void WaterHeaterStratified_Impl::autosizeSourceSideDesignFlowRate() {
      bool result = setString(OS_WaterHeater_StratifiedFields::SourceSideDesignFlowRate, "autosize");
      OS_ASSERT(result);
    }

    bool WaterHeaterStratified_Impl::setIndirectWaterHeatingRecoveryTime(double indirectWaterHeatingRecoveryTime) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::IndirectWaterHeatingRecoveryTime, indirectWaterHeatingRecoveryTime);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNumberofNodes(int numberofNodes) {
      bool result = setInt(OS_WaterHeater_StratifiedFields::NumberofNodes, numberofNodes);
      return result;
    }

    bool WaterHeaterStratified_Impl::setAdditionalDestratificationConductivity(double additionalDestratificationConductivity) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::AdditionalDestratificationConductivity, additionalDestratificationConductivity);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode1AdditionalLossCoefficient(double node1AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node1AdditionalLossCoefficient, node1AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode2AdditionalLossCoefficient(double node2AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node2AdditionalLossCoefficient, node2AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode3AdditionalLossCoefficient(double node3AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node3AdditionalLossCoefficient, node3AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode4AdditionalLossCoefficient(double node4AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node4AdditionalLossCoefficient, node4AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode5AdditionalLossCoefficient(double node5AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node5AdditionalLossCoefficient, node5AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode6AdditionalLossCoefficient(double node6AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node6AdditionalLossCoefficient, node6AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode7AdditionalLossCoefficient(double node7AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node7AdditionalLossCoefficient, node7AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode8AdditionalLossCoefficient(double node8AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node8AdditionalLossCoefficient, node8AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode9AdditionalLossCoefficient(double node9AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node9AdditionalLossCoefficient, node9AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode10AdditionalLossCoefficient(double node10AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node10AdditionalLossCoefficient, node10AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode11AdditionalLossCoefficient(double node11AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node11AdditionalLossCoefficient, node11AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setNode12AdditionalLossCoefficient(double node12AdditionalLossCoefficient) {
      bool result = setDouble(OS_WaterHeater_StratifiedFields::Node12AdditionalLossCoefficient, node12AdditionalLossCoefficient);
      OS_ASSERT(result);
      return result;
    }

    bool WaterHeaterStratified_Impl::setSourceSideFlowControlMode(const std::string& sourceSideFlowControlMode) {
      bool result = setString(OS_WaterHeater_StratifiedFields::SourceSideFlowControlMode, sourceSideFlowControlMode);
      return result;
    }

    bool WaterHeaterStratified_Impl::setIndirectAlternateSetpointTemperatureSchedule(Schedule& schedule) {
      bool result = setSchedule(OS_WaterHeater_StratifiedFields::IndirectAlternateSetpointTemperatureScheduleName, "WaterHeaterStratified",
                                "Indirect Alternate Setpoint Temperature", schedule);
      return result;
    }

    void WaterHeaterStratified_Impl::resetIndirectAlternateSetpointTemperatureSchedule() {
      bool result = setString(OS_WaterHeater_StratifiedFields::IndirectAlternateSetpointTemperatureScheduleName, "");
      OS_ASSERT(result);
    }

    boost::optional<Schedule> WaterHeaterStratified_Impl::optionalHeater1SetpointTemperatureSchedule() const {
      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_WaterHeater_StratifiedFields::Heater1SetpointTemperatureScheduleName);
    }

    boost::optional<Schedule> WaterHeaterStratified_Impl::optionalHeater2SetpointTemperatureSchedule() const {
      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_WaterHeater_StratifiedFields::Heater2SetpointTemperatureScheduleName);
    }

    boost::optional<ZoneHVACComponent> WaterHeaterStratified_Impl::containingZoneHVACComponent() const {
      {
        auto hpwhs = model().getConcreteModelObjects<model::WaterHeaterHeatPump>();
        auto t_Handle = handle();
        for (const auto& hpwh : hpwhs) {
          if (hpwh.tank().handle() == t_Handle) {
            return hpwh;
          }
        }
      }

      {
        auto hpwhs = model().getConcreteModelObjects<model::WaterHeaterHeatPumpWrappedCondenser>();
        auto t_Handle = handle();
        for (const auto& hpwh : hpwhs) {
          if (hpwh.tank().handle() == t_Handle) {
            return hpwh;
          }
        }
      }

      return boost::none;
    }

    boost::optional<double> WaterHeaterStratified_Impl::autosizedTankVolume() const {
      return getAutosizedValue("Design Size Tank Volume", "m3");
    }

    boost::optional<double> WaterHeaterStratified_Impl::autosizedTankHeight() const {
      return getAutosizedValue("Design Size Tank Height", "m");
    }

    boost::optional<double> WaterHeaterStratified_Impl::autosizedHeater1Capacity() const {
      return getAutosizedValue("Design Size Heater 1 Capacity", "W");
    }

    boost::optional<double> WaterHeaterStratified_Impl::autosizedUseSideDesignFlowRate() const {
      return getAutosizedValue("Design Size Use Side Design Flow Rate", "m3/s");
    }

    boost::optional<double> WaterHeaterStratified_Impl::autosizedSourceSideDesignFlowRate() const {
      return getAutosizedValue("Design Size Source Side Design Flow Rate", "m3/s");
    }

    void WaterHeaterStratified_Impl::autosize() {
      autosizeTankVolume();
      autosizeTankHeight();
      autosizeHeater1Capacity();
      autosizeUseSideDesignFlowRate();
      autosizeSourceSideDesignFlowRate();
    }

    void WaterHeaterStratified_Impl::applySizingValues() {
      boost::optional<double> val;
      val = autosizedTankVolume();
      if (val) {
        setTankVolume(val.get());
      }

      val = autosizedTankHeight();
      if (val) {
        setTankHeight(val.get());
      }

      val = autosizedHeater1Capacity();
      if (val) {
        setHeater1Capacity(val.get());
      }

      val = autosizedUseSideDesignFlowRate();
      if (val) {
        setUseSideDesignFlowRate(val.get());
      }

      val = autosizedSourceSideDesignFlowRate();
      if (val) {
        setSourceSideDesignFlowRate(val.get());
      }
    }

    WaterHeaterSizing WaterHeaterStratified_Impl::waterHeaterSizing() const {
      boost::optional<WaterHeaterSizing> waterHeaterSizing_;

      auto sizingObjects = getObject<WaterHeaterStratified>().getModelObjectSources<WaterHeaterSizing>();
      auto predicate = [thisHandle = this->handle()](const auto& siz) {
        try {
          return siz.waterHeater().handle() == thisHandle;
        } catch (...) {
          LOG(Debug, siz.briefDescription() << " is not attached to a WaterHeater object.");
          return false;
        }
      };

      auto it = std::find_if(sizingObjects.cbegin(), sizingObjects.cend(), predicate);
      if (it != sizingObjects.cend()) {
        return *it;
      } else {
        LOG_AND_THROW(briefDescription() << " missing WaterHeater:Sizing object.");
      }
    }

    // Loop connections

    unsigned WaterHeaterStratified_Impl::supplyInletPort() const {
      return OS_WaterHeater_StratifiedFields::UseSideInletNodeName;
    }

    unsigned WaterHeaterStratified_Impl::supplyOutletPort() const {
      return OS_WaterHeater_StratifiedFields::UseSideOutletNodeName;
    }

    unsigned WaterHeaterStratified_Impl::demandInletPort() const {
      return OS_WaterHeater_StratifiedFields::SourceSideInletNodeName;
    }

    unsigned WaterHeaterStratified_Impl::demandOutletPort() const {
      return OS_WaterHeater_StratifiedFields::SourceSideOutletNodeName;
    }

    boost::optional<PlantLoop> WaterHeaterStratified_Impl::plantLoop() const {
      if (m_plantLoop) {
        return m_plantLoop;
      } else {
        // Note: checking for supply side only isn't sufficient for this object, as it can be on the supply of two plant loops
        // Here we only want to return the Use Side one, so we need to filter out the source Side one
        boost::optional<PlantLoop> sourceSidePlantLoop = this->sourceSidePlantLoop();

        std::vector<PlantLoop> plantLoops = this->model().getConcreteModelObjects<PlantLoop>();

        for (const auto& elem : plantLoops) {
          OptionalPlantLoop plantLoop = elem.optionalCast<PlantLoop>();
          if (plantLoop) {
            // Check that the component is on the supply side of the PlantLoop
            if (plantLoop->supplyComponent(this->handle())) {
              // Skip the sourceSide one
              if (sourceSidePlantLoop) {
                if (plantLoop->handle() == sourceSidePlantLoop->handle()) {
                  continue;
                }
              }
              m_plantLoop = plantLoop;

              return plantLoop;
            }
          }
        }
      }

      return boost::none;
    }

    boost::optional<PlantLoop> WaterHeaterStratified_Impl::secondaryPlantLoop() const {
      if (m_secondaryPlantLoop) {
        return m_secondaryPlantLoop;
      } else {

        // Regular case, it's on the demand side: use the base class implementation
        m_secondaryPlantLoop = WaterToWaterComponent_Impl::secondaryPlantLoop();
        if (m_secondaryPlantLoop) {
          return m_secondaryPlantLoop;
        }

        // Less common case: it's also on the supply side (like for the PlantLoop one)
        // so we explicitly ensure this is connected to the **Source** Side Nodes
        if (auto sourceSideOutletModelObject_ = sourceSideOutletModelObject()) {
          if (auto sourceSideOutletHVACComponent_ = sourceSideOutletModelObject_->optionalCast<HVACComponent>()) {
            auto plantLoops = this->model().getConcreteModelObjects<PlantLoop>();

            for (const auto& plantLoop : plantLoops) {
              if (!plantLoop.supplyComponents(plantLoop.supplyInletNode(), sourceSideOutletHVACComponent_.get()).empty()) {
                m_secondaryPlantLoop = plantLoop;
                return plantLoop;
              }
            }
          }
        }
      }

      return boost::none;
    }

    bool WaterHeaterStratified_Impl::removeFromSecondaryPlantLoop() {

      if (auto plant_ = secondaryPlantLoop()) {
        m_secondaryPlantLoop = boost::none;

        auto outletNode = sourceSideOutletModelObject()->cast<HVACComponent>();

        auto supplyNode = plant_->supplyInletNode();
        if (!plant_->supplyComponents(supplyNode, outletNode).empty()) {
          return HVACComponent_Impl::removeFromLoop(supplyNode, plant_->supplyOutletNode(), demandInletPort(), demandOutletPort());
        } else {
          return HVACComponent_Impl::removeFromLoop(plant_->demandInletNode(), plant_->demandOutletNode(), demandInletPort(), demandOutletPort());
        }
      }

      return false;
    }

    bool WaterHeaterStratified_Impl::addToNode(Node& node) {
      boost::optional<PlantLoop> t_plantLoop = node.plantLoop();

      if (t_plantLoop) {
        if (t_plantLoop->supplyComponent(node.handle())) {
          // If these is already a use side Plant Loop
          boost::optional<PlantLoop> useSidePlant = this->useSidePlantLoop();
          if (useSidePlant) {
            // And it's not the same as the node's loop
            if (t_plantLoop.get() != useSidePlant.get()) {
              // Then try to add it to the source side one
              LOG(Warn, "Calling addToSecondaryNode to connect it to the source side loop for " << briefDescription());
              return this->addToSourceSideNode(node);
            }
          }
        }
      }

      // All other cases, call the base class implementation
      return WaterToWaterComponent_Impl::addToNode(node);
    }

    bool WaterHeaterStratified_Impl::addToSourceSideNode(Node& node) {
      auto thisModelObject = getObject<ModelObject>();
      auto t_plantLoop = node.plantLoop();

      boost::optional<unsigned> componentInletPort = demandInletPort();
      boost::optional<unsigned> componentOutletPort = demandOutletPort();

      boost::optional<HVACComponent> systemStartComponent;
      boost::optional<HVACComponent> systemEndComponent;

      if (node.getImpl<Node_Impl>()->isConnected(thisModelObject)) {
        return false;
      }

      if (t_plantLoop) {
        if (t_plantLoop->supplyComponent(node.handle())) {

          systemStartComponent = t_plantLoop->supplyInletNode();
          systemEndComponent = t_plantLoop->supplyOutletNode();

          removeFromSourceSidePlantLoop();
        } else if (t_plantLoop->demandComponent(node.handle())) {
          systemStartComponent = t_plantLoop->demandInletNode();
          systemEndComponent = t_plantLoop->demandOutletNode();

          removeFromSourceSidePlantLoop();
        }
      }

      if (systemStartComponent && systemEndComponent && componentOutletPort && componentInletPort) {
        return HVACComponent_Impl::addToNode(node, systemStartComponent.get(), systemEndComponent.get(), componentInletPort.get(),
                                             componentOutletPort.get());
      }

      return false;
    }

    // Name aliases

    boost::optional<PlantLoop> WaterHeaterStratified_Impl::sourceSidePlantLoop() const {
      return secondaryPlantLoop();
    }

    boost::optional<PlantLoop> WaterHeaterStratified_Impl::useSidePlantLoop() const {
      return plantLoop();
    }

    bool WaterHeaterStratified_Impl::removeFromSourceSidePlantLoop() {
      return removeFromSecondaryPlantLoop();
    }

    boost::optional<ModelObject> WaterHeaterStratified_Impl::useSideInletModelObject() const {
      return supplyInletModelObject();
    }

    boost::optional<ModelObject> WaterHeaterStratified_Impl::useSideOutletModelObject() const {
      return supplyOutletModelObject();
    }

    boost::optional<ModelObject> WaterHeaterStratified_Impl::sourceSideInletModelObject() const {
      return demandInletModelObject();
    }

    boost::optional<ModelObject> WaterHeaterStratified_Impl::sourceSideOutletModelObject() const {
      return demandOutletModelObject();
    }

    ComponentType WaterHeaterStratified_Impl::componentType() const {

      if (isHeater1CapacityAutosized() || ((heater1Capacity().get() + heater2Capacity()) > 0.01)) {
        // If there is a capacity, and it's not zero, it's heating
        return ComponentType::Heating;
      }

      // Capacity is zero: it's a storage tank! get the Source loop
      if (boost::optional<PlantLoop> p_ = secondaryPlantLoop()) {
        return p_->componentType();

        // We also need to check if the tank isn't attached to a HPWH, in which case we return Heating
        // There's some magic later that'll put the HPWH itself and not the tank on the PlantEquipmentOperation,
        // see function operationSchemeComponent()
      } else if (boost::optional<ZoneHVACComponent> zoneComp_ = containingZoneHVACComponent()) {
        // We don't have to explicitly check if it's a WaterHeaterHeatPump or WaterHeaterHeatPumpWrappedCondenser since
        // these are the only two types of zoneHVACComponent that could have a WaterHeater object, but this might change in the future
        IddObjectType zoneCompIdd = zoneComp_->iddObjectType();
        if ((zoneCompIdd == openstudio::IddObjectType::OS_WaterHeater_HeatPump)
            || (zoneCompIdd == openstudio::IddObjectType::OS_WaterHeater_HeatPump_WrappedCondenser)) {
          return ComponentType::Heating;
        } else {
          OS_ASSERT(false);
          return ComponentType::None;
        }
      }

      // It isn't connected to a source side, and has zero capacity, and not part of HPWH => it's a buffer tank => NONE
      return ComponentType::None;
    }

    std::vector<FuelType> WaterHeaterStratified_Impl::coolingFuelTypes() const {
      std::set<FuelType> result;

      // TODO: only if cap == 0?
      if (boost::optional<PlantLoop> p_ = secondaryPlantLoop()) {
        auto plantFuelTypes = p_->coolingFuelTypes();
        result.insert(plantFuelTypes.begin(), plantFuelTypes.end());
      }

      return {result.begin(), result.end()};
    }

    std::vector<FuelType> WaterHeaterStratified_Impl::heatingFuelTypes() const {

      std::set<FuelType> result;

      if (isHeater1CapacityAutosized() || ((heater1Capacity().get() + heater2Capacity()) > 0.01)) {
        // If there is a capacity, and it's not zero, it's heating
        result.insert(FuelType(heaterFuelType()));
      }
      if (boost::optional<PlantLoop> p_ = secondaryPlantLoop()) {
        auto plantFuelTypes = p_->heatingFuelTypes();
        result.insert(plantFuelTypes.begin(), plantFuelTypes.end());
      }
      if (auto zoneComp_ = containingZoneHVACComponent()) {
        // The only accepted types are the WaterHeaterHeatPump or WaterHeaterHeatPumpWrappedCondenser so skip the checks
        result.insert(FuelType::Electricity);
      }

      return {result.begin(), result.end()};
    }

    std::vector<AppGFuelType> WaterHeaterStratified_Impl::appGHeatingFuelTypes() const {

      std::set<AppGFuelType> result;

      if (isHeater1CapacityAutosized() || ((heater1Capacity().get() + heater2Capacity()) > 0.01)) {
        // If there is a capacity, and it's not zero, it's heating
        result.insert(convertFuelTypeToAppG(FuelType(heaterFuelType())));
      }
      if (boost::optional<PlantLoop> p_ = secondaryPlantLoop()) {
        auto plantFuelTypes = p_->appGHeatingFuelTypes();
        result.insert(plantFuelTypes.begin(), plantFuelTypes.end());
      }
      if (auto zoneComp_ = containingZoneHVACComponent()) {
        // The only accepted types are the WaterHeaterHeatPump or WaterHeaterHeatPumpWrappedCondenser so skip the checks
        result.insert(AppGFuelType::HeatPump);
      }

      return {result.begin(), result.end()};
    }

  }  // namespace detail

  WaterHeaterStratified::WaterHeaterStratified(const Model& model) : WaterToWaterComponent(WaterHeaterStratified::iddObjectType(), model) {
    OS_ASSERT(getImpl<detail::WaterHeaterStratified_Impl>());

    setTankVolume(0.1893);  // autosizable
    setTankHeight(1.4);     // autosizable

    setTankShape("VerticalCylinder");
    setMaximumTemperatureLimit(82.22);
    setHeaterPriorityControl("MasterSlave");

    ScheduleRuleset setpoint_schedule_1(model);
    setpoint_schedule_1.defaultDaySchedule().addValue(Time(0, 24, 0, 0), 48.89);

    ScheduleRuleset setpoint_schedule_2(model);
    setpoint_schedule_2.defaultDaySchedule().addValue(Time(0, 24, 0, 0), 48.89);

    setHeater1SetpointTemperatureSchedule(setpoint_schedule_1);
    setHeater1DeadbandTemperatureDifference(2);
    setHeater1Capacity(4500);
    setHeater1Height(1);

    setHeater2SetpointTemperatureSchedule(setpoint_schedule_2);
    setHeater2DeadbandTemperatureDifference(5);
    setHeater2Capacity(4500);
    setHeater2Height(0);

    setHeaterFuelType(FuelType::Electricity);
    setHeaterThermalEfficiency(0.98);

    setOffCycleParasiticFuelConsumptionRate(10.0);
    setOffCycleParasiticFuelType(FuelType::Electricity);
    setOffCycleParasiticHeatFractiontoTank(0);
    setOffCycleParasiticHeight(0);

    setOnCycleParasiticFuelConsumptionRate(10.0);
    setOnCycleParasiticFuelType(FuelType::Electricity);
    setOnCycleParasiticHeatFractiontoTank(0);
    setOnCycleParasiticHeight(0);

    setAmbientTemperatureIndicator("Schedule");
    ScheduleRuleset amb_schedule(model);
    amb_schedule.defaultDaySchedule().addValue(Time(0, 24, 0, 0), 22.0);
    setAmbientTemperatureSchedule(amb_schedule);

    setSkinLossFractiontoZone(1);

    setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(0.846);

    setOffCycleFlueLossCoefficienttoAmbientTemperature(0);
    setOffCycleFlueLossFractiontoZone(1);

    setUseSideEffectiveness(1.0);
    setUseSideInletHeight(0);
    autocalculateUseSideOutletHeight();
    setSourceSideEffectiveness(1.0);
    autocalculateSourceSideInletHeight();
    setSourceSideOutletHeight(0);
    setInletMode("Fixed");
    autosizeUseSideDesignFlowRate();
    autosizeSourceSideDesignFlowRate();
    setIndirectWaterHeatingRecoveryTime(1.5);
    setEndUseSubcategory("General");

    setNumberofNodes(6);
    setAdditionalDestratificationConductivity(0.1);
    setNode1AdditionalLossCoefficient(0.15);
    setNode2AdditionalLossCoefficient(0);
    setNode3AdditionalLossCoefficient(0);
    setNode4AdditionalLossCoefficient(0);
    setNode5AdditionalLossCoefficient(0);
    setNode6AdditionalLossCoefficient(0.1);
    setNode7AdditionalLossCoefficient(0);
    setNode8AdditionalLossCoefficient(0);
    setNode9AdditionalLossCoefficient(0);
    setNode10AdditionalLossCoefficient(0);
    setNode11AdditionalLossCoefficient(0);
    setNode12AdditionalLossCoefficient(0);

    setSourceSideFlowControlMode("IndirectHeatPrimarySetpoint");

    WaterHeaterSizing waterHeaterSizing(*this);
  }

  IddObjectType WaterHeaterStratified::iddObjectType() {
    return {IddObjectType::OS_WaterHeater_Stratified};
  }

  std::vector<std::string> WaterHeaterStratified::tankShapeValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::TankShape);
  }

  std::vector<std::string> WaterHeaterStratified::heaterPriorityControlValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::HeaterPriorityControl);
  }

  std::vector<std::string> WaterHeaterStratified::heaterFuelTypeValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::HeaterFuelType);
  }

  std::vector<std::string> WaterHeaterStratified::offCycleParasiticFuelTypeValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::OffCycleParasiticFuelType);
  }

  std::vector<std::string> WaterHeaterStratified::onCycleParasiticFuelTypeValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::OnCycleParasiticFuelType);
  }

  std::vector<std::string> WaterHeaterStratified::ambientTemperatureIndicatorValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::AmbientTemperatureIndicator);
  }

  std::vector<std::string> WaterHeaterStratified::inletModeValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::InletMode);
  }

  std::vector<std::string> WaterHeaterStratified::sourceSideFlowControlModeValues() {
    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_WaterHeater_StratifiedFields::SourceSideFlowControlMode);
  }

  std::string WaterHeaterStratified::endUseSubcategory() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->endUseSubcategory();
  }

  boost::optional<double> WaterHeaterStratified::tankVolume() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->tankVolume();
  }

  bool WaterHeaterStratified::isTankVolumeAutosized() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->isTankVolumeAutosized();
  }

  boost::optional<double> WaterHeaterStratified::tankHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->tankHeight();
  }

  bool WaterHeaterStratified::isTankHeightAutosized() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->isTankHeightAutosized();
  }

  std::string WaterHeaterStratified::tankShape() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->tankShape();
  }

  boost::optional<double> WaterHeaterStratified::tankPerimeter() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->tankPerimeter();
  }

  double WaterHeaterStratified::maximumTemperatureLimit() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->maximumTemperatureLimit();
  }

  std::string WaterHeaterStratified::heaterPriorityControl() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heaterPriorityControl();
  }

  Schedule WaterHeaterStratified::heater1SetpointTemperatureSchedule() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater1SetpointTemperatureSchedule();
  }

  double WaterHeaterStratified::heater1DeadbandTemperatureDifference() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater1DeadbandTemperatureDifference();
  }

  boost::optional<double> WaterHeaterStratified::heater1Capacity() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater1Capacity();
  }

  bool WaterHeaterStratified::isHeater1CapacityAutosized() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->isHeater1CapacityAutosized();
  }

  double WaterHeaterStratified::heater1Height() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater1Height();
  }

  Schedule WaterHeaterStratified::heater2SetpointTemperatureSchedule() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater2SetpointTemperatureSchedule();
  }

  double WaterHeaterStratified::heater2DeadbandTemperatureDifference() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater2DeadbandTemperatureDifference();
  }

  double WaterHeaterStratified::heater2Capacity() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater2Capacity();
  }

  double WaterHeaterStratified::heater2Height() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heater2Height();
  }

  std::string WaterHeaterStratified::heaterFuelType() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heaterFuelType();
  }

  double WaterHeaterStratified::heaterThermalEfficiency() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->heaterThermalEfficiency();
  }

  double WaterHeaterStratified::offCycleParasiticFuelConsumptionRate() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->offCycleParasiticFuelConsumptionRate();
  }

  std::string WaterHeaterStratified::offCycleParasiticFuelType() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->offCycleParasiticFuelType();
  }

  double WaterHeaterStratified::offCycleParasiticHeatFractiontoTank() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->offCycleParasiticHeatFractiontoTank();
  }

  double WaterHeaterStratified::offCycleParasiticHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->offCycleParasiticHeight();
  }

  double WaterHeaterStratified::onCycleParasiticFuelConsumptionRate() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->onCycleParasiticFuelConsumptionRate();
  }

  std::string WaterHeaterStratified::onCycleParasiticFuelType() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->onCycleParasiticFuelType();
  }

  double WaterHeaterStratified::onCycleParasiticHeatFractiontoTank() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->onCycleParasiticHeatFractiontoTank();
  }

  double WaterHeaterStratified::onCycleParasiticHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->onCycleParasiticHeight();
  }

  std::string WaterHeaterStratified::ambientTemperatureIndicator() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->ambientTemperatureIndicator();
  }

  boost::optional<Schedule> WaterHeaterStratified::ambientTemperatureSchedule() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->ambientTemperatureSchedule();
  }

  boost::optional<ThermalZone> WaterHeaterStratified::ambientTemperatureThermalZone() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->ambientTemperatureThermalZone();
  }

  boost::optional<std::string> WaterHeaterStratified::ambientTemperatureOutdoorAirNodeName() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->ambientTemperatureOutdoorAirNodeName();
  }

  boost::optional<double> WaterHeaterStratified::uniformSkinLossCoefficientperUnitAreatoAmbientTemperature() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->uniformSkinLossCoefficientperUnitAreatoAmbientTemperature();
  }

  double WaterHeaterStratified::skinLossFractiontoZone() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->skinLossFractiontoZone();
  }

  boost::optional<double> WaterHeaterStratified::offCycleFlueLossCoefficienttoAmbientTemperature() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->offCycleFlueLossCoefficienttoAmbientTemperature();
  }

  double WaterHeaterStratified::offCycleFlueLossFractiontoZone() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->offCycleFlueLossFractiontoZone();
  }

  boost::optional<double> WaterHeaterStratified::peakUseFlowRate() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->peakUseFlowRate();
  }

  boost::optional<Schedule> WaterHeaterStratified::useFlowRateFractionSchedule() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useFlowRateFractionSchedule();
  }

  boost::optional<Schedule> WaterHeaterStratified::coldWaterSupplyTemperatureSchedule() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->coldWaterSupplyTemperatureSchedule();
  }

  double WaterHeaterStratified::useSideEffectiveness() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideEffectiveness();
  }

  double WaterHeaterStratified::useSideInletHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideInletHeight();
  }

  boost::optional<double> WaterHeaterStratified::useSideOutletHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideOutletHeight();
  }

  bool WaterHeaterStratified::isUseSideOutletHeightAutocalculated() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->isUseSideOutletHeightAutocalculated();
  }

  double WaterHeaterStratified::sourceSideEffectiveness() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideEffectiveness();
  }

  boost::optional<double> WaterHeaterStratified::sourceSideInletHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideInletHeight();
  }

  bool WaterHeaterStratified::isSourceSideInletHeightAutocalculated() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->isSourceSideInletHeightAutocalculated();
  }

  double WaterHeaterStratified::sourceSideOutletHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideOutletHeight();
  }

  std::string WaterHeaterStratified::inletMode() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->inletMode();
  }

  boost::optional<double> WaterHeaterStratified::useSideDesignFlowRate() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideDesignFlowRate();
  }

  bool WaterHeaterStratified::isUseSideDesignFlowRateAutosized() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->isUseSideDesignFlowRateAutosized();
  }

  boost::optional<double> WaterHeaterStratified::sourceSideDesignFlowRate() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideDesignFlowRate();
  }

  bool WaterHeaterStratified::isSourceSideDesignFlowRateAutosized() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->isSourceSideDesignFlowRateAutosized();
  }

  double WaterHeaterStratified::indirectWaterHeatingRecoveryTime() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->indirectWaterHeatingRecoveryTime();
  }

  int WaterHeaterStratified::numberofNodes() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->numberofNodes();
  }

  double WaterHeaterStratified::additionalDestratificationConductivity() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->additionalDestratificationConductivity();
  }

  double WaterHeaterStratified::node1AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node1AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node2AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node2AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node3AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node3AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node4AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node4AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node5AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node5AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node6AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node6AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node7AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node7AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node8AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node8AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node9AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node9AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node10AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node10AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node11AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node11AdditionalLossCoefficient();
  }

  double WaterHeaterStratified::node12AdditionalLossCoefficient() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->node12AdditionalLossCoefficient();
  }

  std::string WaterHeaterStratified::sourceSideFlowControlMode() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideFlowControlMode();
  }

  boost::optional<Schedule> WaterHeaterStratified::indirectAlternateSetpointTemperatureSchedule() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->indirectAlternateSetpointTemperatureSchedule();
  }

  bool WaterHeaterStratified::setEndUseSubcategory(const std::string& endUseSubcategory) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setEndUseSubcategory(endUseSubcategory);
  }

  bool WaterHeaterStratified::setTankVolume(double tankVolume) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setTankVolume(tankVolume);
  }

  void WaterHeaterStratified::autosizeTankVolume() {
    getImpl<detail::WaterHeaterStratified_Impl>()->autosizeTankVolume();
  }

  bool WaterHeaterStratified::setTankHeight(double tankHeight) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setTankHeight(tankHeight);
  }

  void WaterHeaterStratified::autosizeTankHeight() {
    getImpl<detail::WaterHeaterStratified_Impl>()->autosizeTankHeight();
  }

  bool WaterHeaterStratified::setTankShape(const std::string& tankShape) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setTankShape(tankShape);
  }

  bool WaterHeaterStratified::setTankPerimeter(double tankPerimeter) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setTankPerimeter(tankPerimeter);
  }

  void WaterHeaterStratified::resetTankPerimeter() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetTankPerimeter();
  }

  bool WaterHeaterStratified::setMaximumTemperatureLimit(double maximumTemperatureLimit) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setMaximumTemperatureLimit(maximumTemperatureLimit);
  }

  bool WaterHeaterStratified::setHeaterPriorityControl(const std::string& heaterPriorityControl) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeaterPriorityControl(heaterPriorityControl);
  }

  bool WaterHeaterStratified::setHeater1SetpointTemperatureSchedule(Schedule& schedule) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater1SetpointTemperatureSchedule(schedule);
  }

  bool WaterHeaterStratified::setHeater1DeadbandTemperatureDifference(double heater1DeadbandTemperatureDifference) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater1DeadbandTemperatureDifference(heater1DeadbandTemperatureDifference);
  }

  bool WaterHeaterStratified::setHeater1Capacity(double heater1Capacity) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater1Capacity(heater1Capacity);
  }

  void WaterHeaterStratified::autosizeHeater1Capacity() {
    getImpl<detail::WaterHeaterStratified_Impl>()->autosizeHeater1Capacity();
  }

  bool WaterHeaterStratified::setHeater1Height(double heater1Height) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater1Height(heater1Height);
  }

  bool WaterHeaterStratified::setHeater2SetpointTemperatureSchedule(Schedule& schedule) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater2SetpointTemperatureSchedule(schedule);
  }

  bool WaterHeaterStratified::setHeater2DeadbandTemperatureDifference(double heater2DeadbandTemperatureDifference) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater2DeadbandTemperatureDifference(heater2DeadbandTemperatureDifference);
  }

  bool WaterHeaterStratified::setHeater2Capacity(double heater2Capacity) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater2Capacity(heater2Capacity);
  }

  bool WaterHeaterStratified::setHeater2Height(double heater2Height) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeater2Height(heater2Height);
  }

  bool WaterHeaterStratified::setHeaterFuelType(const FuelType& heaterFuelType) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeaterFuelType(heaterFuelType);
  }

  bool WaterHeaterStratified::setHeaterFuelType(const std::string& heaterFuelType) {
    try {
      return setHeaterFuelType(FuelType{heaterFuelType});
    } catch (std::runtime_error& e) {
      LOG(Debug, e.what());
      return false;
    }
  }

  bool WaterHeaterStratified::setHeaterThermalEfficiency(double heaterThermalEfficiency) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setHeaterThermalEfficiency(heaterThermalEfficiency);
  }

  bool WaterHeaterStratified::setOffCycleParasiticFuelConsumptionRate(double offCycleParasiticFuelConsumptionRate) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOffCycleParasiticFuelConsumptionRate(offCycleParasiticFuelConsumptionRate);
  }

  bool WaterHeaterStratified::setOffCycleParasiticFuelType(const FuelType& offCycleParasiticFuelType) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOffCycleParasiticFuelType(offCycleParasiticFuelType);
  }

  bool WaterHeaterStratified::setOffCycleParasiticFuelType(const std::string& offCycleParasiticFuelType) {
    try {
      return setOffCycleParasiticFuelType(FuelType{offCycleParasiticFuelType});
    } catch (std::runtime_error& e) {
      LOG(Debug, e.what());
      return false;
    }
  }

  bool WaterHeaterStratified::setOffCycleParasiticHeatFractiontoTank(double offCycleParasiticHeatFractiontoTank) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOffCycleParasiticHeatFractiontoTank(offCycleParasiticHeatFractiontoTank);
  }

  bool WaterHeaterStratified::setOffCycleParasiticHeight(double offCycleParasiticHeight) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOffCycleParasiticHeight(offCycleParasiticHeight);
  }

  bool WaterHeaterStratified::setOnCycleParasiticFuelConsumptionRate(double onCycleParasiticFuelConsumptionRate) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOnCycleParasiticFuelConsumptionRate(onCycleParasiticFuelConsumptionRate);
  }

  bool WaterHeaterStratified::setOnCycleParasiticFuelType(const FuelType& onCycleParasiticFuelType) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOnCycleParasiticFuelType(onCycleParasiticFuelType);
  }

  bool WaterHeaterStratified::setOnCycleParasiticFuelType(const std::string& onCycleParasiticFuelType) {
    try {
      return setOnCycleParasiticFuelType(FuelType{onCycleParasiticFuelType});
    } catch (std::runtime_error& e) {
      LOG(Debug, e.what());
      return false;
    }
  }

  bool WaterHeaterStratified::setOnCycleParasiticHeatFractiontoTank(double onCycleParasiticHeatFractiontoTank) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOnCycleParasiticHeatFractiontoTank(onCycleParasiticHeatFractiontoTank);
  }

  bool WaterHeaterStratified::setOnCycleParasiticHeight(double onCycleParasiticHeight) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOnCycleParasiticHeight(onCycleParasiticHeight);
  }

  bool WaterHeaterStratified::setAmbientTemperatureIndicator(const std::string& ambientTemperatureIndicator) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setAmbientTemperatureIndicator(ambientTemperatureIndicator);
  }

  bool WaterHeaterStratified::setAmbientTemperatureSchedule(Schedule& schedule) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setAmbientTemperatureSchedule(schedule);
  }

  void WaterHeaterStratified::resetAmbientTemperatureSchedule() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetAmbientTemperatureSchedule();
  }

  bool WaterHeaterStratified::setAmbientTemperatureThermalZone(const ThermalZone& thermalZone) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setAmbientTemperatureThermalZone(thermalZone);
  }

  void WaterHeaterStratified::resetAmbientTemperatureThermalZone() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetAmbientTemperatureThermalZone();
  }

  bool WaterHeaterStratified::setAmbientTemperatureOutdoorAirNodeName(const std::string& ambientTemperatureOutdoorAirNodeName) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setAmbientTemperatureOutdoorAirNodeName(ambientTemperatureOutdoorAirNodeName);
  }

  void WaterHeaterStratified::resetAmbientTemperatureOutdoorAirNodeName() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetAmbientTemperatureOutdoorAirNodeName();
  }

  bool WaterHeaterStratified::setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(
    double uniformSkinLossCoefficientperUnitAreatoAmbientTemperature) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(
      uniformSkinLossCoefficientperUnitAreatoAmbientTemperature);
  }

  void WaterHeaterStratified::resetUniformSkinLossCoefficientperUnitAreatoAmbientTemperature() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetUniformSkinLossCoefficientperUnitAreatoAmbientTemperature();
  }

  bool WaterHeaterStratified::setSkinLossFractiontoZone(double skinLossFractiontoZone) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setSkinLossFractiontoZone(skinLossFractiontoZone);
  }

  bool WaterHeaterStratified::setOffCycleFlueLossCoefficienttoAmbientTemperature(double offCycleFlueLossCoefficienttoAmbientTemperature) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOffCycleFlueLossCoefficienttoAmbientTemperature(
      offCycleFlueLossCoefficienttoAmbientTemperature);
  }

  void WaterHeaterStratified::resetOffCycleFlueLossCoefficienttoAmbientTemperature() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetOffCycleFlueLossCoefficienttoAmbientTemperature();
  }

  bool WaterHeaterStratified::setOffCycleFlueLossFractiontoZone(double offCycleFlueLossFractiontoZone) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setOffCycleFlueLossFractiontoZone(offCycleFlueLossFractiontoZone);
  }

  bool WaterHeaterStratified::setPeakUseFlowRate(double peakUseFlowRate) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setPeakUseFlowRate(peakUseFlowRate);
  }

  void WaterHeaterStratified::resetPeakUseFlowRate() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetPeakUseFlowRate();
  }

  bool WaterHeaterStratified::setUseFlowRateFractionSchedule(Schedule& schedule) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setUseFlowRateFractionSchedule(schedule);
  }

  void WaterHeaterStratified::resetUseFlowRateFractionSchedule() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetUseFlowRateFractionSchedule();
  }

  bool WaterHeaterStratified::setColdWaterSupplyTemperatureSchedule(Schedule& schedule) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setColdWaterSupplyTemperatureSchedule(schedule);
  }

  void WaterHeaterStratified::resetColdWaterSupplyTemperatureSchedule() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetColdWaterSupplyTemperatureSchedule();
  }

  bool WaterHeaterStratified::setUseSideEffectiveness(double useSideEffectiveness) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setUseSideEffectiveness(useSideEffectiveness);
  }

  bool WaterHeaterStratified::setUseSideInletHeight(double useSideInletHeight) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setUseSideInletHeight(useSideInletHeight);
  }

  bool WaterHeaterStratified::setUseSideOutletHeight(double useSideOutletHeight) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setUseSideOutletHeight(useSideOutletHeight);
  }

  void WaterHeaterStratified::autocalculateUseSideOutletHeight() {
    getImpl<detail::WaterHeaterStratified_Impl>()->autocalculateUseSideOutletHeight();
  }

  bool WaterHeaterStratified::setSourceSideEffectiveness(double sourceSideEffectiveness) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setSourceSideEffectiveness(sourceSideEffectiveness);
  }

  bool WaterHeaterStratified::setSourceSideInletHeight(double sourceSideInletHeight) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setSourceSideInletHeight(sourceSideInletHeight);
  }

  void WaterHeaterStratified::autocalculateSourceSideInletHeight() {
    getImpl<detail::WaterHeaterStratified_Impl>()->autocalculateSourceSideInletHeight();
  }

  bool WaterHeaterStratified::setSourceSideOutletHeight(double sourceSideOutletHeight) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setSourceSideOutletHeight(sourceSideOutletHeight);
  }

  bool WaterHeaterStratified::setInletMode(const std::string& inletMode) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setInletMode(inletMode);
  }

  bool WaterHeaterStratified::setUseSideDesignFlowRate(double useSideDesignFlowRate) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setUseSideDesignFlowRate(useSideDesignFlowRate);
  }

  void WaterHeaterStratified::autosizeUseSideDesignFlowRate() {
    getImpl<detail::WaterHeaterStratified_Impl>()->autosizeUseSideDesignFlowRate();
  }

  bool WaterHeaterStratified::setSourceSideDesignFlowRate(double sourceSideDesignFlowRate) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setSourceSideDesignFlowRate(sourceSideDesignFlowRate);
  }

  void WaterHeaterStratified::autosizeSourceSideDesignFlowRate() {
    getImpl<detail::WaterHeaterStratified_Impl>()->autosizeSourceSideDesignFlowRate();
  }

  bool WaterHeaterStratified::setIndirectWaterHeatingRecoveryTime(double indirectWaterHeatingRecoveryTime) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setIndirectWaterHeatingRecoveryTime(indirectWaterHeatingRecoveryTime);
  }

  bool WaterHeaterStratified::setNumberofNodes(int numberofNodes) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNumberofNodes(numberofNodes);
  }

  bool WaterHeaterStratified::setAdditionalDestratificationConductivity(double additionalDestratificationConductivity) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setAdditionalDestratificationConductivity(additionalDestratificationConductivity);
  }

  bool WaterHeaterStratified::setNode1AdditionalLossCoefficient(double node1AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode1AdditionalLossCoefficient(node1AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode2AdditionalLossCoefficient(double node2AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode2AdditionalLossCoefficient(node2AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode3AdditionalLossCoefficient(double node3AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode3AdditionalLossCoefficient(node3AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode4AdditionalLossCoefficient(double node4AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode4AdditionalLossCoefficient(node4AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode5AdditionalLossCoefficient(double node5AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode5AdditionalLossCoefficient(node5AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode6AdditionalLossCoefficient(double node6AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode6AdditionalLossCoefficient(node6AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode7AdditionalLossCoefficient(double node7AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode7AdditionalLossCoefficient(node7AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode8AdditionalLossCoefficient(double node8AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode8AdditionalLossCoefficient(node8AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode9AdditionalLossCoefficient(double node9AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode9AdditionalLossCoefficient(node9AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode10AdditionalLossCoefficient(double node10AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode10AdditionalLossCoefficient(node10AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode11AdditionalLossCoefficient(double node11AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode11AdditionalLossCoefficient(node11AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setNode12AdditionalLossCoefficient(double node12AdditionalLossCoefficient) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setNode12AdditionalLossCoefficient(node12AdditionalLossCoefficient);
  }

  bool WaterHeaterStratified::setSourceSideFlowControlMode(const std::string& sourceSideFlowControlMode) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setSourceSideFlowControlMode(sourceSideFlowControlMode);
  }

  bool WaterHeaterStratified::setIndirectAlternateSetpointTemperatureSchedule(Schedule& schedule) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->setIndirectAlternateSetpointTemperatureSchedule(schedule);
  }

  void WaterHeaterStratified::resetIndirectAlternateSetpointTemperatureSchedule() {
    getImpl<detail::WaterHeaterStratified_Impl>()->resetIndirectAlternateSetpointTemperatureSchedule();
  }

  /// @cond
  WaterHeaterStratified::WaterHeaterStratified(std::shared_ptr<detail::WaterHeaterStratified_Impl> impl) : WaterToWaterComponent(std::move(impl)) {}
  /// @endcond

  boost::optional<double> WaterHeaterStratified::autosizedTankVolume() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->autosizedTankVolume();
  }

  boost::optional<double> WaterHeaterStratified::autosizedTankHeight() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->autosizedTankHeight();
  }

  boost::optional<double> WaterHeaterStratified::autosizedHeater1Capacity() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->autosizedHeater1Capacity();
  }

  boost::optional<double> WaterHeaterStratified::autosizedUseSideDesignFlowRate() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->autosizedUseSideDesignFlowRate();
  }

  boost::optional<double> WaterHeaterStratified::autosizedSourceSideDesignFlowRate() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->autosizedSourceSideDesignFlowRate();
  }

  WaterHeaterSizing WaterHeaterStratified::waterHeaterSizing() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->waterHeaterSizing();
  }

  // Helper
  bool WaterHeaterStratified::addToSourceSideNode(Node& node) {
    return getImpl<detail::WaterHeaterStratified_Impl>()->addToSourceSideNode(node);
  }

  // Name aliases
  boost::optional<PlantLoop> WaterHeaterStratified::sourceSidePlantLoop() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSidePlantLoop();
  }

  boost::optional<PlantLoop> WaterHeaterStratified::useSidePlantLoop() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useSidePlantLoop();
  }

  bool WaterHeaterStratified::removeFromSourceSidePlantLoop() {
    return getImpl<detail::WaterHeaterStratified_Impl>()->removeFromSourceSidePlantLoop();
  }

  boost::optional<ModelObject> WaterHeaterStratified::useSideInletModelObject() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideInletModelObject();
  }

  boost::optional<ModelObject> WaterHeaterStratified::useSideOutletModelObject() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->useSideOutletModelObject();
  }

  boost::optional<ModelObject> WaterHeaterStratified::sourceSideInletModelObject() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideInletModelObject();
  }

  boost::optional<ModelObject> WaterHeaterStratified::sourceSideOutletModelObject() const {
    return getImpl<detail::WaterHeaterStratified_Impl>()->sourceSideOutletModelObject();
  }

}  // namespace model
}  // namespace openstudio
